package model

import (
	j_log "config-client-v1/init/j-log"
	"config-client-v1/utils/tools"
	"context"
	"encoding/json"
	"fmt"
	"github.com/olekukonko/tablewriter"
	"github.com/robinson/gos7"
	"github.com/simonvetter/modbus"
	"os"
	"runtime/debug"
	"time"
)

type PLCConfig struct {
	Type    string `json:"type"`     // PLC类型 [S7-1200,S7-1500,汇川]
	IP      string `json:"ip"`       // IP
	Port    int    `json:"port"`     // 端口
	PDU     int    `json:"pdu"`      // PDU大小限
	CPURack int    `json:"cpu_rack"` // CPU机架
	CPUSlot int    `json:"cpu_slot"` // CPU插槽
	Endian  string `json:"endian"`   // 属性大小端[big,little]
}

func plcSnap7WorkerWrite(ctxPLCSnap7 context.Context, ppe *PPE, client *gos7.Client, frame *Frame, stopChan chan int) {
	defer func() {
		if err := recover(); err != nil {
			j_log.LogError([]interface{}{"plcSnap7WorkerWrite panic", err})
			fmt.Println(string(debug.Stack()))
		}
		j_log.LogInfo([]interface{}{fmt.Sprintf("plcSnap7WorkerWrite线程已退出 Frame:%d", frame.ID)})
	}()
	for {
		select {
		case <-ctxPLCSnap7.Done(): // 监听取消信号
			return
		default:
			if frame.SendType == 1 {
				startAddr := frame.StartAddr

				list := make([]*FrameItemParam, 0)
				err := json.Unmarshal([]byte(frame.JsonDetail), &list)
				if err != nil {
					j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, json解析失败 frame.JsonDetail; %s", frame.ID, err.Error())})
					return
				}
				data, err := CreateFrameRawData(list)
				if err != nil {
					j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, 发送帧生成帧原始数据失败; %s", frame.ID, err.Error())})
					return
				}
				if len(data) != frame.Quantity {
					j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, PLC写入的点位个数与帧字节长度不符;quantity=%d, data=%v", frame.ID, frame.Quantity, data)})
					return
				}
				err = (*client).AGWriteDB(frame.DBNumber, int(startAddr), frame.Quantity, data)
				if err != nil {
					j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, AGWriteDB error:%v", frame.ID, err.Error())})
					return
				}
				j_log.LogInfo([]interface{}{fmt.Sprintf("frame_id=%d, 写入点位data=%v", frame.ID, data)})

				time.Sleep(time.Duration(frame.ScanRate) * time.Millisecond)
			} else if frame.SendType == 2 { // 事件触发
				match, err := IsTriggerConditionMatched(frame)
				if err != nil {
					j_log.LogError([]interface{}{fmt.Sprintf("[%s] frame_id=%d, IsTriggerConditionMatched error %s", frame.Name, frame.ID, err.Error())})
					return
				}
				if match {
					j_log.LogInfo([]interface{}{fmt.Sprintf("[%s] frame_id=%d, 事件触发帧满足条件", frame.Name, frame.ID)})
					if time.Now().Sub(frame.LastTriggerTime).Seconds() < float64(frame.TriggerInterval) {
						j_log.LogInfo([]interface{}{fmt.Sprintf("[%s] frame_id=%d, 事件触发帧距离下一次触发时间间隔未到, 跳过", frame.Name, frame.ID)})
						time.Sleep(1000 * time.Millisecond)
						continue
					}

					startAddr := frame.StartAddr

					list := make([]*FrameItemParam, 0)
					err = json.Unmarshal([]byte(frame.JsonDetail), &list)
					if err != nil {
						j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, json解析失败 frame.JsonDetail; %s", frame.ID, err.Error())})
						return
					}
					data, err := CreateFrameRawData(list)
					if err != nil {
						j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, 发送帧生成帧原始数据失败; %s", frame.ID, err.Error())})
						return
					}
					if len(data) != frame.Quantity {
						j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, PLC写入的点位个数与帧字节长度不符;quantity=%d, data=%v", frame.ID, frame.Quantity, data)})
						return
					}
					err = (*client).AGWriteDB(frame.DBNumber, int(startAddr), frame.Quantity, data)
					if err != nil {
						j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, AGWriteDB error:%v", frame.ID, err.Error())})
						return
					}
					j_log.LogInfo([]interface{}{fmt.Sprintf("frame_id=%d, 写入点位data=%v", frame.ID, data)})

					frame.LastTriggerTime = time.Now()
				} else {
					j_log.LogInfo([]interface{}{fmt.Sprintf("[%s] frame_id=%d, 事件触发帧不满足条件, 跳过", frame.Name, frame.ID)})
				}
				time.Sleep(1000 * time.Millisecond)
			}
		}
	}
}

func plcSnap7WorkerRead(ctxPLCSnap7 context.Context, ppe *PPE, client *gos7.Client, frame *Frame, stopChan chan int) {
	defer func() {
		if err := recover(); err != nil {
			j_log.LogError([]interface{}{"plcSnap7WorkerRead panic", err})
			fmt.Println(string(debug.Stack()))
		}
		j_log.LogInfo([]interface{}{fmt.Sprintf("plcSnap7WorkerRead线程已退出 Frame:%d", frame.ID)})
		stopChan <- frame.ID
	}()
	for {
		select {
		case <-ctxPLCSnap7.Done(): // 监听取消信号
			return
		default:
			now := tools.GetNow()
			startAddr := frame.StartAddr
			buffer := make([]byte, frame.Quantity)
			err := (*client).AGReadDB(frame.DBNumber, int(startAddr), frame.Quantity, buffer)
			if err != nil {
				j_log.LogError([]interface{}{fmt.Sprintf("%s:%s AGReadDB error:%v", ppe.Name, frame.Name, err.Error())})
				return
			}

			data := make([][]string, 0)
			/*--- 变量解析开始 ---*/
			messageList := make([]*QueueValue, 0)
			frameItem := make([]*FrameItemParam, 0)
			err = json.Unmarshal([]byte(frame.JsonDetail), &frameItem)
			if err != nil {
				j_log.LogError([]interface{}{"json.Unmarshal ERROR frame.JsonDetail", err.Error()})
				return
			}
			for _, item := range frameItem {
				var value float64
				var valueStr string
				switch item.SpecType {
				case "uint8":
					v := buffer[item.Start]
					value = float64(v)
				case "int8":
					v := buffer[item.Start]
					value = float64(int8(v))
				case "uint16":
					rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 2)
					v := tools.BytesToUint16(rawBytes, item.Endian)
					value = float64(v)
				case "int16":
					rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 2)
					v := tools.BytesToUint16(rawBytes, item.Endian)
					value = float64(int16(v))
				case "uint32":
					rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 4)
					v := tools.BytesToUint32(rawBytes, item.Endian)
					value = float64(v)
				case "int32":
					rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 4)
					v := tools.BytesToUint32(rawBytes, item.Endian)
					value = float64(int32(v))
				case "uint64":
					rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 8)
					v := tools.BytesToUint64(rawBytes, item.Endian)
					value = float64(v)
				case "int64":
					rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 8)
					v := tools.BytesToUint64(rawBytes, item.Endian)
					value = float64(int64(v))
				case "float32":
					v := tools.BytesToFloat32(buffer[item.Start:item.End+1], item.Endian)
					value = float64(v)
				case "float64":
					v := tools.BytesToFloat64(buffer[item.Start:item.End+1], item.Endian)
					value = v
				case "bool":
					if buffer[item.Start] == 0 {
						value = float64(0)
					} else {
						value = float64(1)
					}
				}
				data = append(data, []string{
					item.Addr,
					item.FieldName,
					item.SpecType,
					tools.ByteArrayToHexStr(buffer[item.Start : item.End+1]),
					fmt.Sprintf("%v", tools.FormatFloat(value)),
					fmt.Sprintf("%v", item.Start),
					fmt.Sprintf("%v", item.End),
					fmt.Sprintf("%v", valueStr),
				})

				// 变量保存到 redis 队列
				queueData := QueueValue{}
				queueData.SpecType = item.SpecType
				queueData.Addr = item.Addr
				queueData.FieldName = item.FieldName
				queueData.Value = value
				queueData.CreateTime = &now
				messageList = append(messageList, &queueData)
			}

			if len(messageList) > 0 {
				go VariablesMap.PushMessageToClients(messageList)
			}
			/*--- 变量解析结束 ---*/
			if len(data) > 0 {
				j_log.LogInfo([]interface{}{fmt.Sprintf("frame_id=%d, 读取点位", frame.ID)})
				table := tablewriter.NewWriter(os.Stdout)
				table.SetHeader([]string{"地址", "变量名", "类型", "原始数据", "解析值", "START", "END", "string"})
				for _, v := range data {
					table.Append(v)
				}
				table.Render()
			}
			time.Sleep(time.Duration(frame.ScanRate) * time.Millisecond)
		}
	}
}

func (ppe *PPE) RunPLCSnap7() {
	defer func() {
		if err := recover(); err != nil {
			j_log.LogError([]interface{}{"RunPLCSnap7 panic", err})
			fmt.Println(string(debug.Stack()))
		}

		if ppe.RunningState == 3 {
			j_log.LogInfo([]interface{}{"【已停止】", ppe.ProtocolName, ppe.Name})
			ppe.RunningState = 4 // 设备停止
			ppe.UnRegister()
			_ = ppe.UpdateRunningState(4, "设备已停止")
			return
		}

		j_log.LogInfo([]interface{}{fmt.Sprintf("%s", ppe.Name), "3秒后重连"})
		ppe.RunningState = 5 // 设备异常
		ppe.Detail = "正在尝试重连..."
		_ = ppe.UpdateRunningState(5, "正在尝试重连...")
		time.Sleep(3 * time.Second)

		if ppe.RunningState == 3 {
			j_log.LogInfo([]interface{}{"【已停止】", ppe.ProtocolName, ppe.Name})
			ppe.RunningState = 4 // 设备停止
			_ = ppe.UpdateRunningState(4, "FS设备已停止")
			ppe.UnRegister()
			return
		}

		ppe.RunPLCSnap7()
	}()
	ppe.Register()
	config := PLCConfig{}
	err := json.Unmarshal([]byte(ppe.Config), &config)
	if err != nil {
		j_log.LogError([]interface{}{fmt.Sprintf("%s", ppe.Name), "RunPLCSnap7 json解析失败:" + err.Error()})
		return
	}

	handler := gos7.NewTCPClientHandler(fmt.Sprintf("%s:%d", config.IP, config.Port), config.CPURack, config.CPUSlot)
	handler.Timeout = 200 * time.Second
	handler.IdleTimeout = 200 * time.Second
	//handler.Logger = log.New(os.Stdout, "tcp: ", log.LstdFlags)
	err = handler.Connect()
	if err != nil {
		j_log.LogError([]interface{}{fmt.Sprintf("%s", ppe.Name), "RunPLCSnap7 连接失败:" + err.Error()})
		return
	}
	defer handler.Close()
	client := gos7.NewClient(handler)
	j_log.LogInfo([]interface{}{fmt.Sprintf("PLC连接成功 %s:%d", config.IP, config.Port)})
	ppe.Detail = "已启动"
	_ = ppe.UpdateRunningState(2, "已启动")
	ppe.RunningState = 2 // 启动成功
	ppe.PLCSnap7Client = &client

	ctxPLCSnap7, cancel := context.WithCancel(context.Background())
	defer cancel()
	stopChan := make(chan int, 1000)
	for _, frame := range ppe.FrameList {
		if frame.Status != 1 {
			continue
		}
		switch frame.FrameType {
		case "read":
			go plcSnap7WorkerRead(ctxPLCSnap7, ppe, &client, frame, stopChan)
		case "write":
			if frame.SendType >= 1 && frame.SendType <= 2 {
				go plcSnap7WorkerWrite(ctxPLCSnap7, ppe, &client, frame, stopChan)
			}
		default:
			j_log.LogError([]interface{}{fmt.Sprintf("%s:%s", ppe.Name, frame.Name), "无效的PLC帧类型", frame.FrameType})
		}
	}

	for {
		select {
		case frameID := <-stopChan:
			j_log.LogInfo([]interface{}{fmt.Sprintf("%s:frame_id=%d", ppe.Name, frameID), "子协程发送停止信号"})
			return
		default:
			if ppe.RunningState == 3 {
				return
			}
			time.Sleep(500 * time.Millisecond)
		}
	}
}
func (ppe *PPE) RunPLCHuiChuan() {
	defer func() {
		if err := recover(); err != nil {
			j_log.LogError([]interface{}{"RunPLCHuiChuan panic", err})
			fmt.Println(string(debug.Stack()))
		}

		if ppe.RunningState == 3 {
			j_log.LogInfo([]interface{}{"【已停止】", ppe.ProtocolName, ppe.Name})
			ppe.RunningState = 4 // 设备停止
			_ = ppe.UpdateRunningState(4, "已停止")
			ppe.UnRegister()
			return
		}

		j_log.LogInfo([]interface{}{fmt.Sprintf("%s", ppe.Name), "3秒后重连"})
		ppe.RunningState = 0 // 设备异常
		ppe.Detail = "正在尝试重连..."
		_ = ppe.UpdateRunningState(5, "正在尝试重连...")
		time.Sleep(3 * time.Second)

		if ppe.RunningState == 3 {
			j_log.LogInfo([]interface{}{"【已停止】", ppe.ProtocolName, ppe.Name})
			ppe.RunningState = 4 // 设备停止
			_ = ppe.UpdateRunningState(4, "已停止")
			ppe.UnRegister()
			return
		}

		ppe.RunPLCHuiChuan()
	}()
	ppe.Register()
	config := PLCConfig{}
	err := json.Unmarshal([]byte(ppe.Config), &config)
	if err != nil {
		j_log.LogError([]interface{}{fmt.Sprintf("%s", ppe.Name), "PLCConfig json解析失败:" + err.Error()})
		return
	}

	url := fmt.Sprintf("tcp://%s:%d", config.IP, config.Port)
	client, err := modbus.NewClient(&modbus.ClientConfiguration{
		URL:     url,
		Timeout: 1 * time.Second,
	})
	if err != nil {
		j_log.LogError([]interface{}{url, "modbus.NewClient error:" + err.Error()})
		ppe.Detail = fmt.Sprintf("%v %v modbus.NewClient error: %v", ppe.Name, url, err.Error())
		return
	}
	err = client.Open()
	if err != nil {
		j_log.LogError([]interface{}{url, "modbus client.Open error:" + err.Error()})
		ppe.Detail = fmt.Sprintf("%v %v modbus client.Open error: %v", ppe.Name, url, err.Error())
		return
	}
	defer client.Close()
	ppe.RunningState = 2
	ppe.ModbusTCPClient = client
	j_log.LogInfo([]interface{}{fmt.Sprintf("【已启动】%s:%s", ppe.ProtocolName, ppe.Name)})
	ppe.Detail = "已启动"
	_ = ppe.UpdateRunningState(2, "已启动")

	ctxModbus, cancel := context.WithCancel(context.Background())
	defer cancel()

	stopChan := make(chan int, 1000)
	for _, frame := range ppe.FrameList {
		if frame.Status != 1 {
			continue
		}
		switch frame.FunctionCode {
		case "01":
			go modbus0x01(ctxModbus, ppe, client, frame, stopChan)
		case "02":
			go modbus0x02(ctxModbus, ppe, client, frame, stopChan)
		case "03":
			go modbus0x03(ctxModbus, ppe, client, frame, stopChan)
		case "04":
			go modbus0x04(ctxModbus, ppe, client, frame, stopChan)
		case "05":
			if frame.SendType >= 1 && frame.SendType <= 2 {
				go modbus0x05(ctxModbus, ppe, client, frame, stopChan)
			}
		case "06":
			if frame.SendType >= 1 && frame.SendType <= 2 {
				go modbus0x06(ctxModbus, ppe, client, frame, stopChan)
			}
		case "0F":
			if frame.SendType >= 1 && frame.SendType <= 2 {
				go modbus0x0F(ctxModbus, ppe, client, frame, stopChan)
			}
		case "10":
			if frame.SendType >= 1 && frame.SendType <= 2 {
				go modbus0x10(ctxModbus, ppe, client, frame, stopChan)
			}
		default:
			j_log.LogInfo([]interface{}{fmt.Sprintf("[%s][%s][%s] 无效的功能码", ppe.Name, frame.Name, frame.FunctionCode)})
			continue
		}
		time.Sleep(100 * time.Millisecond)
	}
	for {
		select {
		case frameID := <-stopChan:
			j_log.LogInfo([]interface{}{fmt.Sprintf("%s:frame_id=%d", ppe.Name, frameID), "子协程发送停止信号"})
			return
		default:
			if ppe.RunningState == 3 {
				return
			}
			time.Sleep(500 * time.Millisecond)
		}
	}
}
